/**
 * File: CurtainGraph.java
 * Created by: mhaimel
 * Created on: Mar 4, 2010
 * CVS:  $Id: CurtainGraph.java 1.0 Mar 4, 2010 4:35:35 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.curtain;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Graph;
import uk.ac.ebi.curtain.model.graph.GraphAccess.TouchEach;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.model.graph.NodeEdge;
import uk.ac.ebi.curtain.model.graph.impl.ConcurrentGraph;
import uk.ac.ebi.curtain.model.graph.io.GraphReader;
import uk.ac.ebi.curtain.model.graph.io.GraphWriter;

/**
 * @author mhaimel
 *
 */
public class CurtainGraph {
	private Log log = LogFactory.getLog(this.getClass());
	private final Graph<ContigInfo, ReadWrapper> graph;
	private final Integer categories;
	private final AtomicLong nextCtgId;
	
	private Log getLog() {
		return log;
	}
	
	public void persist(File f) throws IOException{
		getLog().debug("Persist Graph to " + f);
		GraphWriter<ContigInfo, ReadWrapper> writer = _buildWriter();
		writer.write(f);
	}
	
	public CurtainGraph load(File f) throws IOException {
		getLog().debug("Load Graph from " + f);
		GraphReader<ContigInfo, ReadWrapper> reader = _buildReader();
		StopWatch sw = new StopWatch();
		sw.start();
		getLog().debug(sw + ": Load Graph from " +f);
		Graph<ContigInfo, ReadWrapper> nGraph = reader.read(f);
		sw.stop();
        getLog().debug(sw+": Finished loading Graph from "+f);
		return new CurtainGraph(categories, nGraph);
	}
	private GraphReader<ContigInfo, ReadWrapper> _buildReader() {
        return _buildReader(graph);
    }

	private GraphReader<ContigInfo, ReadWrapper> _buildReader(Graph<ContigInfo, ReadWrapper> template) {
		GraphReader<ContigInfo, ReadWrapper> reader = new GraphReader<ContigInfo, ReadWrapper>(template);
		reader.setArcValueMapper(new ReadWrapperSequMapper());
		reader.setNodeValueMapper(new ContigMapper());
		return reader;
	}

	private GraphWriter<ContigInfo, ReadWrapper> _buildWriter() {
		GraphWriter<ContigInfo, ReadWrapper> writer = new GraphWriter<ContigInfo, ReadWrapper>(graph);
		writer.setArcValueMapper(new ReadWrapperSequMapper());
		writer.setNodeValueMapper(new ContigMapper());
		return writer;
	}

	@SuppressWarnings("unchecked")
	public CurtainGraph(Integer categories, Long contigs) {
		this(categories,(Graph)new ConcurrentGraph<ContigInfo, ReadWrapper>(contigs));
	}
	
	public CurtainGraph(Integer categories, Graph<ContigInfo, ReadWrapper> graph) {
		this.categories = categories;
		this.graph = graph;
		this.nextCtgId = new AtomicLong(0);
	}

	/**
	 * @see {@link #createArcEnd2Start(Node, Node)}
	 * @param left
	 * @param right
	 * @return Arc instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcEnd2Start(ContigInfo left, ContigInfo right){
		return createArcEnd2Start(graph.getNode(left.getId()), graph.getNode(right.getId()));
	}
	/**
	 * 
	 * @see {@link #createArcEnd2End(Node, Node)}
	 * @param left
	 * @param right
	 * @return Arc instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcEnd2End(ContigInfo left, ContigInfo right){
		return createArcEnd2End(graph.getNode(left.getId()), graph.getNode(right.getId()));
	}
	/**
	 * 
	 * @see {@link #createArcStart2Start(Node, Node)}
	 * @param left
	 * @param right
	 * @return Arc instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcStart2Start(ContigInfo left, ContigInfo right){
		return createArcStart2Start(graph.getNode(left.getId()), graph.getNode(right.getId()));
	}

	private ReadWrapper _newInstanceRead(Long id) {
		return new ReadWrapper(id,getCategories());
	}
	
	/**
	 * 
	 * Creates an Arc from the right end of the left node to the left end of the right node<br>
	 * {@code --left-->(arc)<--right-- }
	 * 
	 * @see {@link #createArc(Node, Node, boolean, boolean)}
	 * @param leftEnd
	 * @param rightStart
	 * @return {@link Arc} instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcEnd2Start(Node<ContigInfo, ReadWrapper> leftEnd,Node<ContigInfo, ReadWrapper> rightStart) {
		return createArc(leftEnd, rightStart, NodeEdge.End, NodeEdge.Start);
	}
	
	/**
	 * 
	 * Creates an Arc from the right end of the left node to the right end of the right node<br>
	 * e.g. Link the end of a forward to the end of a reverse strand
	 * {@code --left-->arc ... --right--<arc }
	 * 
	 * @see {@link #createArc(Node, Node, boolean, boolean)}
	 * @param leftEnd
	 * @param rightStart
	 * @return {@link Arc} instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcEnd2End(Node<ContigInfo, ReadWrapper> leftEnd,Node<ContigInfo, ReadWrapper> rightStart) {
		return createArc(leftEnd, rightStart, NodeEdge.End, NodeEdge.End);
	}
	
	
	/**
	 * 
	 * Creates an Arc from the left end of the left node to the left end of the right node<br>
	 * e.g. Link the start of a reverse to the start of a forward strand
	 * {@code arc>--left--  .. arc<--right-- }
	 * 
	 * @see {@link #createArc(Node, Node, boolean, boolean)}
	 * @param leftEnd
	 * @param rightStart
	 * @return {@link Arc} instance of Arc
	 */
	public Arc<ContigInfo, ReadWrapper> createArcStart2Start(Node<ContigInfo, ReadWrapper> leftEnd,Node<ContigInfo, ReadWrapper> rightStart) {
		return createArc(leftEnd, rightStart, NodeEdge.Start, NodeEdge.Start);
	}
	
	
	

	/**
	 * @param arc
	 * @see uk.ac.ebi.curtain.model.graph.Graph#removeArc(uk.ac.ebi.curtain.model.graph.Arc)
	 */
	public void removeArc(Arc<ContigInfo, ReadWrapper> arc) {
		graph.removeArc(arc);
	}

	/**
	 * @param left
	 * @param right
	 * @param leftNodePos
	 * @param rightNodePos
	 * @return {@link Arc} instance of Arc
	 * @see uk.ac.ebi.curtain.model.graph.Graph#createArc(uk.ac.ebi.curtain.model.graph.Node, uk.ac.ebi.curtain.model.graph.Node, NodeEdge, NodeEdge)
	 */
	public Arc<ContigInfo, ReadWrapper> createArc(Node<ContigInfo, ReadWrapper> left,Node<ContigInfo, ReadWrapper> right, NodeEdge leftNodePos,NodeEdge rightNodePos) {
		Arc<ContigInfo, ReadWrapper> arc = graph.createArc(left, right, leftNodePos, rightNodePos);
		ReadWrapper value = arc.getValue();
		if(value == null){
			value = _newInstanceRead(arc.getId());
			if(!arc.getAtomicValue().compareAndSet(null, value)){
				// already set in the mean time - retrieve latest value
				value = arc.getValue();
			}
		}
		return arc;
	}

	/**
	 * @param id
	 * @param sequenceLength TODO
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getEmptyNode(java.lang.Long)
	 */
	public Node<ContigInfo, ReadWrapper> getEmptyNode(Long id, Long sequenceLength) {
		Node<ContigInfo, ReadWrapper> node = graph.getEmptyNode(id);
		if(null != node.getValue()){
			throw new IllegalStateException("Value found - Empty node expected for id " + id+":" + node.getValue());
		}
		ContigInfo info = new ContigInfo(node.getId(), sequenceLength, getCategories());
		node.setValue(info);
		return node;
	}
	
	/**
	 * @param sequenceLength TODO
	 * @param id
	 * @return
	 * @see {@link #getEmptyNode(Long, Long)}
	 */
	public Node<ContigInfo, ReadWrapper> getNextEmptyNode(Long sequenceLength) {
		return getEmptyNode(this.nextCtgId.getAndIncrement(), sequenceLength);
	}

	/**
	 * 
	 * @see uk.ac.ebi.curtain.model.graph.Graph#reindexArcs()
	 */
	public void reindexArcs() {
		graph.reindexArcs();
	}

	/**
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getArcCount()
	 */
	public Long getArcCount() {
		return graph.getArcCount();
	}
	
	
	

	/**
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getArcSize()
	 */
	public Long getArcSize() {
		return graph.getArcSize();
	}

	/**
	 * @param id
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getArc(java.lang.Long)
	 */
	public Arc<ContigInfo, ReadWrapper> getArc(Long id) {
		return graph.getArc(id);
	}

	/**
	 * @param id
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getNode(java.lang.Long)
	 */
	public Node<ContigInfo, ReadWrapper> getNode(Long id) {
		return graph.getNode(id);
	}

	/**
	 * @return
	 * @see uk.ac.ebi.curtain.model.graph.Graph#getNodeSize()
	 */
	public Long getNodeSize() {
		return graph.getNodeSize();
	}	
	
	public Graph<ContigInfo, ReadWrapper> getGraph(){
		return graph;
	}
	
	public Integer getCategories(){
		return categories;
	}
	
	public String graphNodeSummary() {
		final AtomicLong total = new AtomicLong(0);
		graph.processNodes(new TouchEach<Node<ContigInfo, ReadWrapper>>(){
			@Override
			public void touch(Node<ContigInfo, ReadWrapper> n) {
				total.addAndGet(n.getValue().getLength());
		}});
		return "Total length" + total+"; Count: " + graph.getNodeSize();
	}

	/**
	 * @param obj
	 * @return
	 * @see java.lang.Integer#equals(java.lang.Object)
	 */
	public boolean equals(Object obj) {
		return categories.equals(obj);
	}

	/**
	 * @return
	 * @see java.lang.Integer#hashCode()
	 */
	public int hashCode() {
		return categories.hashCode();
	}
	
	
	
}
